package com.YKFuse.wrapper.calculate;

import com.YKFuse.annotion.YKFuse;
import com.YKFuse.bean.operation.pool.YKFusePoolOperation;
import com.YKFuse.wrapper.MethodDelayWrapper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author yuank
 * @version 1.0.0
 * @date 2025/2/13 13:46:42
 *
 * 对bean进行包装 所有方法默认被拦截(语法性质) 用于支持 时间窗口 入池边界值 降级返回方法
 *
 * 关于bean 此对象是一个bean生成一个本代理对象
 * 关于拦截 创建此代理的bean、所有的方法都会被拦截 ->于是需要set进行校验是否是需要熔断支持的方法
 *
 */
@Slf4j
@Data
public class BeanMethodCalculateProxy implements MethodInterceptor {
    /**
     * 被代理的自身 (反射三件套剩余两个会自动处理 前提是只需要代理对象的引用即可)
     */
    private Object bean;
    //    private Method method;
    //    private Object[] objects;
    /**
     * 处于熔断池状态的方法
     */
    private HashSet<Method> methodSet;
    /**
     * 用于记录每个需要熔断支持的方法的当前情况
     */
    private HashMap<Method, MethodCalculator> methodCalculatorMap;
    /**
     * 熔断池
     */
    private YKFusePoolOperation poolOperation;
    /**
     * 下一次reset计数情况的时间
     *   >1微秒为单位 2默认初始化就OK因为第一次为0符合逻辑 3bean方法代理全局级别的存在
     */
    private long windowRequestTime;

    public BeanMethodCalculateProxy(Object bean, Set<Method> methodSet, YKFusePoolOperation poolOperation) {
        this.bean = bean;
        this.poolOperation = poolOperation;
    }


    @Override
    // Method和MethodProxy 都是被代理方法 但是MethodProxy是优化版本指针直指避免反射消耗
    public Object intercept(Object bean, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //校验是否需要熔断器支持
        if (methodSet.contains(method)) {
            if (!methodCalculatorMap.containsKey(method)){// 计数初始化
                // 支持 熔断触发 失败/请求总数 的比值
                methodCalculatorMap
                        .put( method,
                              new MethodCalculator(method.getAnnotation(YKFuse.class).fuseRate()));
            }

        //熔断判断

            //已在池中-降级
            if (poolOperation.isInPoor(method)) {
                // 支持 自定义FallbackMethodName
                String fallbackMethodName = method.getAnnotation(YKFuse.class).FallbackMethodName();
                log.info("bean{}的fallbackMethodName:{}已被降级入熔断池处理", fallbackMethodName);
                if (fallbackMethodName.length() != 0) {
                    bean
                            .getClass()
                            .getMethod(fallbackMethodName)
                            .invoke(bean, objects);
                }
                return null;
            }


            //未入池-校验熔断入池比例
            boolean invokeSuccess = true;
            MethodCalculator methodCalculator = methodCalculatorMap.get(method);

            //先执行 try-catch 相当于if-else
            try {
                Object returnValue = methodProxy.invokeSuper(bean, objects);
                invokeSuccess = true;
            } catch (Exception e) {
                invokeSuccess = false;
            }

            finally {
                //支持 处理时间窗口重置的优先 在局部当前下就是专属于一次调用
                final TimeUnit resetTimeUnit = method.getAnnotation(YKFuse.class).timeUnit();
                final long timeLength = method.getAnnotation(YKFuse.class).l();
                final long currentTime = System.currentTimeMillis();
                if (currentTime > windowRequestTime){
                    windowRequestTime = resetTimeUnit.toMillis(timeLength) + currentTime;
                    methodCalculator.reset();
                }

                //成功+1
                if (invokeSuccess){
                    //又没入池又成功 那么可以不需要计算入池了
                    methodCalculator.success();
                    return methodProxy.invokeSuper(bean, objects);

                //失败+1
                } else{
                    methodCalculator.error();

                    //计算熔断情况-考虑入池
                    boolean needFuse = methodCalculator.fuseCheck();
                    // 熔断入池
                    if (needFuse){
                        poolOperation.trigger(new MethodDelayWrapper(bean, objects, method));
                    }
                    // 没到入池的比率要求
                    throw new RuntimeException(String.format(
                            "%s 方法执行失败,但未在时间窗口 %s 内达到需要熔断降级的水平(比值 %s)的情况",
                            method.getName(),
                            "略",  // 这里可以替换成你需要的值
                            method.getAnnotation(YKFuse.class).fuseRate()
                    ));
                }
            }

        }

        //不需要组建的支持？-好吧,放行
        return methodProxy.invokeSuper(bean, objects);
    }
}